function AutoComplete(element, opt) {
    let self = this;
    this.element = element;
    this.opt = opt;
    this.resultList = opt.resultList;
    this.innerData = [];
    this.selectedData = null;
    this.focusPos = 0;
    /**
     * render dropdown menu to choose
     */
    this.render = function () {
        this.element.autocomplete = "off";
        if (!this.resultList) {
            const resultList = document.createElement("ul");
            resultList.classList.add("dropdown-menu");
            if (element.nextSibling) {
                element.parentNode.insertBefore(resultList, element.nextSibling)
            } else {
                element.parentNode.append(resultList);
            }
            this.resultList = resultList;
        }
    };
    /**
     * remove the dropdown menu
     */
    this.remove = function () {
        self.resultList.classList.remove("show");
    };
    /**
     * Render the drawdown menu
     * @private
     */
    this._renderItem = function (item) {
        let li = document.createElement("li");
        li.classList.add("dropdown-item");
        if (typeof item === "string") {
            li.textContent = item;
        } else {
            li.textContent = JSON.stringify(item);
        }
        this.opt.renderCell && (li = this.opt.renderCell(item));
        li.addEventListener("click", () => {
            this.selectedData = item;
            this.callback(item);
            this.remove();
        });
        return li;
    };
    /**
     * When choose the data, how to fill it back to input.
     * Default strategy:
     * if ( opt.mapper)
     * opt.mapper = {"input name":"attribute"}
     * else
     * opt.mapper = {"input name":"input name"}
     */
    this.callback = opt.callback || (data => {
        const elementName = self.element.name;
        opt.mapper || (opt.mapper = {[elementName]: elementName});
        const form = self.element.form;
        let eleArray = form.elements[self.element.name];
        let pos = -1;
        if(eleArray instanceof HTMLFormControlsCollection)
        {
            pos = Array.from(form.elements[self.element.name]).indexOf(self.element);
        }
        Object.entries(opt.mapper).forEach(([key, value]) => {
            let inputs = form.elements[key];
            if (inputs) {
                if (!inputs.length) {
                    inputs.value = data[value] || "";
                }
                if (inputs.length > 1 && inputs.length > pos) {
                    inputs[pos].value = data[value] || "";
                }
            }
        });

    })
    /**
     * Get array data that should be renderer to dropdown
     */
    this.data = opt.data;
    /**
     * match data
     */
    this._match = opt.match || function (value) {
        const q = value.trim().toLowerCase();
        return self.data.filter(item => {
            if (typeof item === "string") {
                return item.toLowerCase().includes(q);
            }
            if (typeof item === "object" && item !== null) {
                return Object.values(item).some(val =>
                    String(val).toLowerCase().includes(q)
                );
            }
            return false;
        });
    };

    this.element.addEventListener("keydown", e => {
        if (e.key === 'Enter') {
            e.preventDefault();
        }
    });
    this.element.addEventListener("keyup", e => {
        const value = element.value.trim();
        if (e.key === "ArrowDown") {
            this.focusPos >= 0 && this.resultList.children.item(this.focusPos).classList.remove("focus");
            this.focusPos++;
            if (this.focusPos >= this.innerData.length)
                this.focusPos = 0;
            this.resultList.children.item(this.focusPos).classList.add("focus");
        } else if (e.key === "ArrowUp") {
            this.focusPos >= 0 && this.resultList.children.item(this.focusPos).classList.remove("focus");
            this.focusPos--;
            if (this.focusPos < 0)
                this.focusPos = this.innerData.length - 1;
            this.resultList.children.item(this.focusPos).classList.add("focus");
        } else if (e.key === "Enter") {
            this.selectedData = this.innerData[this.focusPos];
            this.callback(this.innerData[this.focusPos]);
            this.remove();
        } else if (e.key === "Escape") {
            this.callback(this.selectedData);
            this.remove();
        } else {
            (async () => {
                await this._fetchSuggestions(value);
                this.focusPos = -1;
            })();
        }
    });

    this.element.addEventListener("blur", function () {
        if (self.element.value === "") {
            self.selectedData = {};
            self.callback(self.selectedData)
            self.remove();
        } else if (self.selectedData) {
            self.callback(self.selectedData);
            self.remove();
        }
    });

    this._fetchSuggestions = async function (value) {
        let tmp = await this._match(value);
        self.resultList.innerHTML = "";
        this.innerData = tmp;
        this.innerData?.forEach(item => this.resultList.appendChild(this._renderItem(item)));
        this.innerData && self.resultList.classList.add("show");
        this.focusPos = 0;
    }
    this.render();
}
